import urwid
import os
import weakref
import asyncio
import sys

import utils
from backend.sessionsHandler import SessionsHandler

class CustomButton(urwid.Button):
    signals = ['click', 'cancel', 'delete', 'rename']

    def __init__(self, caption, actionDict, info = None):
        super().__init__(caption)

        self._w = urwid.AttrMap(urwid.SelectableIcon(caption), None, 'reversed')
        self.actionDict = actionDict

        if info:
            self.info = info

    def keypress(self, size, key):
        if key in self.actionDict:
            self._emit(self.actionDict[key])
        else:
            return key


class CustomColumns(urwid.Columns):
    def __init__(self, widget_list, dividechars = 0, info = None):
        super().__init__(widget_list, dividechars)
        if info:
            self.info = info


class UserInterface(SessionsHandler):
    def __init__(self):
        super().__init__(
            self.notification,
            True if (len(sys.argv) > 1 and sys.argv[1] == '1') else False
        )

        self.notifInfo = {'buffer': '', 'timer': 0, 'endTimer': 6}

        self.loop = asyncio.get_event_loop()
        self.main_widget = self.build_main_widget()

        self.mainKeyList = [{'keybind' : self.cfg['keybinds']['upload'],
                             'widget'  : self.build_upload_widget,
                             'input'   : self.handle_keys_null},

                            {'keybind' : self.cfg['keybinds']['download'],
                             'widget'  : self.build_download_widget,
                             'input'   : self.handle_keys_download},

                            {'keybind' : self.cfg['keybinds']['resume'],
                             'widget'  : self.build_resume_widget,
                             'input'   : self.handle_keys_null}]

        palette = [('boldtext', 'default,bold', 'default', 'bold'),
                   ('reversed', 'standout', '')]

        self.urwid_loop = urwid.MainLoop(
            widget = self.main_widget,
            palette = palette,
            handle_mouse = False,
            unhandled_input = self.handle_keys_main,
            event_loop = urwid.AsyncioEventLoop(loop = self.loop))


    def notification(self, inStr: str):
        self.notifInfo['buffer'] = inStr
        self.notifInfo['timer'] = 0


    '''
    Creates the given widget giving it the args it received,
    then it sets urwid_loop's widget and unhandled_input to the ones
    received

    This function is mainly used by urwid.Button signal callback as it can
    have only one callback function
    '''
    def change_widget(self, widget, unhandled_input, user_args: dict = None,
                      key = None):
        # Conditionally set argument
        fun_kwargs = user_args if user_args else {}

        self.urwid_loop.widget = widget(**fun_kwargs)
        self.urwid_loop.unhandled_input = unhandled_input


    def build_main_widget(self):
        def update_info(used_sessions_ref, notif_text_ref, transfer_info_ref):
            local_used_sessions = used_sessions_ref()
            local_notif_text = notif_text_ref()
            local_transfer_info = transfer_info_ref()

            if (not local_used_sessions or not local_notif_text or
                not local_transfer_info):
                '''
                Widget is dead, the main loop must've been destroyed,
                also maybe redundant, if the main loop is destroyed then
                all the widgets will be destroyed, so we only need to check
                one of the widgets
                '''
                return

            local_used_sessions.set_text("[ {} of {} ]".format(
                self.getUsedSessions(),
                self.cfg['remote']['max_sessions']
            ))

            if self.notifInfo['buffer']:
                if not self.notifInfo['timer']: # new notification
                    local_notif_text.set_text(
                        ('reversed', self.notifInfo['buffer'])
                    )

                self.notifInfo['timer'] += 1
                if self.notifInfo['timer'] == self.notifInfo['endTimer']:
                    local_notif_text.set_text('')
                    self.notifInfo['timer'] = 0
                    self.notifInfo['buffer'] = ''

            '''
            Delete finished transfers

            It's possible that the transfer doesn't get deleted when it
            should if we add another transfer that uses the same session file
            inbetween the runs of this loop

            In that case, the label of this button would just get updated along
            with the progress update of the other transfers

            The above paragraph is false because for some reason the label for
            the buttons doesn't change on the screen despite it changing
            when checking the label string of the widget manually

            For the same reason we can't update the transfer progess
            '''

            # Clear finished transfers
            local_transfer_info.contents[:] = [
                x for x in local_transfer_info.contents
                if self.tgSessions[x[0].info['sFile']]['info']['type']]

            # Update progress, doesn't work
            '''
            for i, widget in enumerate(local_transfer_info.contents):
                currentTransfer=self.tgSessions[widget[0].info['sFile']]['info']

                label = "{}\n{}\n{}% - {}".format(
                    "Uploading:" if currentTransfer['type'] == 'upload' else 
                    "Downloading:",
                    '/'.join(currentTransfer['rPath']),
                    currentTransfer['progress'],
                    utils.bytesConvert(currentTransfer['size']))

                local_transfer_info[i].set_label(label)
            '''

            # Add new transfers
            for sFile, data in self.tgSessions.items():
                # If the transfer does not exist in local_transfer_info add it
                if data['info']['type'] and sFile not in [x[0].info['sFile']
                                         for x in local_transfer_info.contents]:
                    label = "{}\n{}\n{}".format(
                        "Uploading:" if data['info']['type'] == 'upload' else
                        "Downloading:",
                        '/'.join(data['info']['rPath']),
                        utils.bytesConvert(data['info']['size']))

                    button = CustomButton(label,
                        {self.cfg['keybinds']['cancel']: 'cancel'},
                        {'sFile': sFile})

                    urwid.connect_signal(button, 'cancel', self.cancel_in_loop,
                        user_args=[sFile, data['info']['rPath']]
                    )

                    local_transfer_info.contents.append((button, pack_option))

            self.loop.call_soon(self.urwid_loop.draw_screen)
            self.loop.call_later(1, update_info, used_sessions_ref,
                                 notif_text_ref, transfer_info_ref
            )

        title = urwid.Text("Remote File Manager", align='center')
        used_sessions = urwid.Text('', align='right')
        transfer_info = urwid.Pile([])
        useless_button = urwid.Button("Current transfers")
        notif_text = urwid.Text('', align='center')
        pack_option = transfer_info.options('pack', None)
        div = urwid.Divider()

        pile = urwid.Pile(
            [title, used_sessions,
             urwid.Columns([useless_button, ('weight', 3, notif_text)], 1),
             div, transfer_info])

        '''
        The reason we don't call this instantly is because urwid_loop that is
        needed by update_info is not yet defined
        '''
        self.loop.call_later(1, update_info,
            weakref.ref(used_sessions), weakref.ref(notif_text),
            weakref.ref(transfer_info)
        )

        return urwid.Filler(pile, 'top')


    def build_upload_widget(self):
        fpath = urwid.Edit(('boldtext', "File Path:\n"))
        rpath = urwid.Edit(('boldtext', "Relative Path:\n"))

        upload = urwid.Button("Upload")
        urwid.connect_signal(upload, 'click', self.upload_in_loop,
            weak_args=[fpath, rpath])

        cancel = urwid.Button("Cancel", self.return_to_main)

        div = urwid.Divider()
        pile = urwid.Pile([fpath, div, rpath, div,
                           urwid.AttrMap(upload, None, focus_map='reversed'),
                           urwid.AttrMap(cancel, None, focus_map='reversed')])

        return urwid.Filler(pile, 'top')


    def build_download_widget(self):
        totalSize = 0

        dpath = urwid.Edit(('boldtext', "Download path: "),
            os.path.join(self.cfg['paths']['data_path'], 'downloads'))

        body = [dpath, urwid.Divider()]

        for i in self.fileDatabase['file']:
            totalSize += i['size']

            button = CustomButton("{}  {}".format(
                                      '/'.join(i['rPath']),
                                      utils.bytesConvert(i['size'])
                                  ),
                                  {'enter' : 'click',
                                   'd'     : 'delete',
                                   'r'     : 'rename'})

            urwid.connect_signal(
                button, 'click', self.download_in_loop,
                weak_args = [dpath], user_args = [i]
            )

            urwid.connect_signal(button, 'rename', self.change_widget,
                user_args = [self.build_rename_widget, self.handle_keys_null,
                             {'fileData': i}]
            )

            urwid.connect_signal(button, 'delete', self.change_widget,
                user_args = [self.build_delete_widget, self.handle_keys_null,
                             {'fileData': i}]
            )

            body.append(button)

        body.insert(0, urwid.Text(
            ('reversed', "Enter to download, d to delete, r to rename - "
                         "{} Total".format(utils.bytesConvert(totalSize))
            )
        ))

        listBox = urwid.ListBox(urwid.SimpleFocusListWalker(body))
        return urwid.Padding(listBox, left=2, right=2)


    '''
    Widget that shows unfinished transfers and prompts the user
    to either resume, ignore or delete each of the transfers.

    When one of these actions is selected for a transfer, that
    transfer gets removed from the widget so that the user can't
    select multiple actions for a transfer.
    '''
    def build_resume_widget(self):
        valid_resume = self.getValidResume()

        if not valid_resume:
            self.urwid_loop.unhandled_input = self.handle_keys_main
            self.notification("No resume information")
            return self.main_widget

        title = urwid.Button(('boldtext', "Cancelled transfers:"))
        div = urwid.Divider()

        pile = urwid.Pile([title, div])
        pack_option = pile.options('pack', None)

        for sFile, info in valid_resume.items():
            transfer_name = urwid.Text("Session {}, '{}' - {}:".format(
                sFile, '/'.join(info['rPath']),
                utils.bytesConvert(info['size'])))

            resume = urwid.Button("Resume")
            urwid.connect_signal(resume, 'click', self.resume_in_loop,
                                 weak_args = [pile],
                                 user_args = [sFile, 1]
            )

            delete = urwid.Button("Delete")
            urwid.connect_signal(delete, 'click', self.resume_in_loop,
                                 weak_args = [pile],
                                 user_args = [sFile, 2]
            )

            option_pile = urwid.Pile([urwid.AttrMap(resume, None,
                                                    focus_map='reversed'),
                                      urwid.AttrMap(delete, None,
                                                    focus_map='reversed')])

            transfer_columns = CustomColumns([('weight', 4, transfer_name),
                                              ('weight', 1, option_pile)], 1,
                                             {'sFile': sFile})

            pile.contents.append((transfer_columns, pack_option))
            pile.contents.append((div, pack_option))

        pile.contents.append((urwid.Button("Done", self.return_to_main),
                              pack_option)
        )

        return urwid.Filler(pile, 'top')


    def build_rename_widget(self, fileData):
        newName = urwid.Edit(('boldtext', "Rename {}:\n".format(
            '/'.join(fileData['rPath']))))

        rename = urwid.Button("Rename")
        urwid.connect_signal(rename, 'click', self.rename_in_loop,
                             weak_args = [newName], user_args = [fileData])

        cancel = urwid.Button("Cancel", self.return_to_main)

        div = urwid.Divider()
        pile = urwid.Pile([newName, div,
                           urwid.AttrMap(rename, None, focus_map='reversed'),
                           urwid.AttrMap(cancel, None, focus_map='reversed')])

        return urwid.Filler(pile, 'top')


    def build_delete_widget(self, fileData):
        confirm_text = urwid.Text(('boldtext',
                                   "Are you sure you want to delete {}?".format(
                                                  '/'.join(fileData['rPath']))))

        delete = urwid.Button("Delete")
        urwid.connect_signal(delete, 'click', self.delete_in_loop,
                             user_args = [fileData])

        cancel = urwid.Button("Cancel", self.return_to_main)

        div = urwid.Divider()
        pile = urwid.Pile([confirm_text, div,
                           urwid.AttrMap(cancel, None, focus_map='reversed'),
                           urwid.AttrMap(cancel, None, focus_map='reversed'),
                           urwid.AttrMap(delete, None, focus_map='reversed'),
                           urwid.AttrMap(cancel, None, focus_map='reversed')])

        return urwid.Filler(pile, 'top')


    def handle_keys_main(self, key):
        if key == 'esc':
            raise urwid.ExitMainLoop

        for i in self.mainKeyList:
            if key == i['keybind']:
                self.urwid_loop.unhandled_input = i['input']
                self.urwid_loop.widget = i['widget']() # build widget everytime
                break # don't check for other keys


    def handle_keys_download(self, key):
        if key == 'q':
            self.return_to_main()


    def handle_keys_null(self, key): pass


    def return_to_main(self, key = None):
        self.urwid_loop.widget = self.main_widget
        self.urwid_loop.unhandled_input = self.handle_keys_main


    def upload_in_loop(self, path, rPath, key):
        self.loop.create_task(self.upload({
            'rPath' : rPath.edit_text.split('/'),
            'path'  : path.edit_text,
        }))

        self.return_to_main()


    def download_in_loop(self, dPath, fileData, key):
        self.loop.create_task(self.download({
            'rPath'    : fileData['rPath'],
            'dPath'    : dPath.edit_text,
            'fileID'   : fileData['fileID'],
            'size'     : fileData['size'],
            'checksum' : fileData['checksum'],
        }))

        self.return_to_main()


    def resume_in_loop(self, pile_widget, sFile, selected, key):
        self.loop.create_task(self.resumeHandler(sFile, selected))

        '''
        Remove current transfer from pile_widget but don't delete
        other widgets
        '''
        pile_widget.contents[:] = [x for x in pile_widget.contents
            if type(x[0]) != CustomColumns or x[0].info['sFile'] != sFile]
        pile_widget.focus_position = 0


    def rename_in_loop(self, rename_widget, fileData, key):
        self.renameInDatabase(fileData, rename_widget.edit_text.split('/'))
        self.return_to_main()


    def delete_in_loop(self, fileData, key):
        self.loop.create_task(self.deleteInDatabase(fileData))
        self.return_to_main()


    def cancel_in_loop(self, sFile, rPath, key):
        self.loop.create_task(self.cancelTransfer(sFile))
        self.notification("Transfer {} cancelled".format('/'.join(rPath)))


if __name__ == "__main__":
    ui = UserInterface()
    ui.urwid_loop.run()
